package com.googlecode.svnj.reports;

import java.io.IOException;
import java.io.OutputStream;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;

import javax.xml.stream.XMLStreamException;
import javax.xml.stream.XMLStreamWriter;

import org.apache.jackrabbit.webdav.DavException;
import org.apache.jackrabbit.webdav.DavResource;
import org.apache.jackrabbit.webdav.WebdavRequest;
import org.apache.jackrabbit.webdav.WebdavResponse;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import org.w3c.dom.Node;
import org.w3c.dom.NodeList;

import com.googlecode.svnj.RepoContext;
import com.googlecode.svnj.XMLUtil;
import com.googlecode.svnj.callbacks.ChangePath;
import com.googlecode.svnj.callbacks.LogMessageCallback;
import com.googlecode.svnj.dav.IO;

public class LogReport extends ReportRequest {

    private static final Logger log = LoggerFactory.getLogger(LogReport.class);
    
    private long startRevision = -1, endRevision = -1;
    private int limit = 0; // unlimited
    /* requested revision properties */
    private List<String> revprops;
    private List<String> paths;
    private boolean allRevprops, noRevprops;
    private boolean discoverChangedPaths, strictNodeHistory, includeMergedRevisions;
    
    @Override
    public String getReportType() {
        return TYPE_LOG;
    }

    private static class ReportLogCallback extends StaxHelper implements LogMessageCallback {
        ReportLogCallback(XMLStreamWriter stream) {
            this.stream = stream;
        }
        private static String elemForAction(char c) {
            switch (c) {
            case 'A': return "S:added-path";
            case 'R': return "S:replaced-path";
            case 'D': return "S:deleted-path";
            case 'M': return "S:modified-path";
            default:
                return null;
            }
        }
        private void writePath(ChangePath p) throws XMLStreamException {
            String elem = elemForAction(p.getAction());
            if (elem == null) return; // ???
            elem(elem);
            if (p.getCopySrcRevision() != -1 && p.getCopySrcPath() != null) {
                att("copyfrom-path", p.getCopySrcPath());
                att("copyfrom-rev", String.valueOf(p.getCopySrcRevision()));
            }
            String kindStr;
            int kind_t = p.getNodeKind();
            if (kind_t == RepoContext.ENTRY_NONE) kindStr = "none";
            else if (kind_t == RepoContext.ENTRY_FILE) kindStr = "file";
            else if (kind_t == RepoContext.ENTRY_DIR) kindStr = "dir";
            else kindStr = "unknown";
            att("node-kind", kindStr);
            att("text-mods", String.valueOf(p.isTextMods()));
            att("props-mods", String.valueOf(p.isPropMods()));
            text(p.getPath(), true);
            end(); // elem
        }
        public void write(ChangePath[] paths, long rev, Map<String,String> props, boolean chilluns) throws XMLStreamException {
            elem("S:log-item");
            elem("D:version-name"); text(String.valueOf(rev), false); end();
            if (props != null) {
                String val = props.get("svn:log");
                if (val != null) {
                    elem("D:comment"); text(val, true); end();
                }
                val = props.get("svn:author");
                if (val != null) {
                    elem("D:creator-displayname"); text(val, true); end();
                }
                val = props.get("svn:date");
                if (val != null) {
                    elem("S:date"); text(val, false); end();
                }
            }
            if (paths != null) {
                for (ChangePath path : paths) writePath(path);
            }
            end(); // log-item
        }
        public void singleMessage(ChangePath[] paths, long rev, Map props, boolean chilluns) {
            try {
                write(paths, rev, props, chilluns);
            } catch (XMLStreamException e) {
                log.error("problem writing log report", e);
                throw new RuntimeException(e);
            }
        }
    }
    @Override
    public void report(WebdavRequest req, WebdavResponse rsp, DavResource resource)
    throws DavException, IOException, XMLStreamException {
        rsp.setHeader("Content-Type", "text/xml; charset=\"utf-8\"");
        OutputStream os = rsp.getOutputStream();
        /* FIXME: before re-inventing the wheel, I'd like to see if there's
         * any goodness to be shared or otherwise co-opted from JavaHL's
         * existing LogMessage / LogReceiver infrastructure:
         * http://code.google.com/p/svnj/issues/detail?id=4
         */
        XMLStreamWriter xs = XMLUtil.initWriter(os, "S:log-report");
        RepoContext rc = IO.get().getRepo(req);
        ReportLogCallback rlc = new ReportLogCallback(xs);
        String[] pa = null, ra = null;
        if (paths != null) pa = (String[]) paths.toArray(new String[0]);
        if (revprops != null) ra = (String[]) revprops.toArray(new String[0]);
        
        rc.getLogs(rlc, pa, startRevision, endRevision, limit, discoverChangedPaths,
                strictNodeHistory, includeMergedRevisions, ra);
        xs.writeEndDocument(); // be sure to close it out
    }    

    @Override
    protected void parse(Node root) {
        NodeList children = root.getChildNodes();
        startRevision = XMLUtil.getChildLong(root, "start-revision", -1L);
        endRevision = XMLUtil.getChildLong(root, "end-revision", -1L);
        limit = XMLUtil.getChildLong(root, "limit", -1L).intValue();
        allRevprops = XMLUtil.hasChild(root, "all-revprops");
        noRevprops = XMLUtil.hasChild(root, "no-revprops");
        discoverChangedPaths = XMLUtil.hasChild(root, "discover-changed-paths");
        strictNodeHistory = XMLUtil.hasChild(root, "strict-node-history");
        includeMergedRevisions = XMLUtil.hasChild(root, "include-merged-revisions");
        revprops = new ArrayList<String>();
        paths = new ArrayList<String>();
        for (int i = 0; i < children.getLength(); i++) {
            Node n = children.item(i);
            String name = n.getLocalName();
            if ("path".equals(name)) {
                String path = n.getTextContent();
                if (path == null) {
                    path = "";
                }
                paths.add(path);
            } else if ("revprop".equals(name)) {
                String prop = n.getTextContent();
                if (prop != null) {
                    revprops.add(prop);
                }
            }
        }        
    }

    public long getStartRevision() {
        return startRevision;
    }

    public long getEndRevision() {
        return endRevision;
    }

    public int getLimit() {
        return limit;
    }

    public List<String> getRevprops() {
        return revprops;
    }

    public List<String> getPaths() {
        return paths;
    }

    public boolean isAllRevprops() {
        return allRevprops;
    }

    public boolean isNoRevprops() {
        return noRevprops;
    }

    public boolean isDiscoverChangedPaths() {
        return discoverChangedPaths;
    }

    public boolean isStrictNodeHistory() {
        return strictNodeHistory;
    }

    public boolean isIncludeMergedRevisions() {
        return includeMergedRevisions;
    }
    
}
